/****************************************************************************

	COPYRIGHT(C) MAXSI SOFTWARE, JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010

    This file is part of Maxsi Engine.

    Maxsi Engine is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Maxsi Engine is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	*/ #include "MaxsiEngineContributors.h" /*

	If you modify this file, please enter your name below and provide contact
	information in MaxsiEngineContributors.h. For more information please see
	MaxsiEngineContributors.h.
	
	Contributors to this file:

	- Jonas 'Sortie' Termansen
	- [your name here]

	MaxsiEngine.dll
	A linkable version of Maxsi Engine

	MaxsiDataContainer.cpp
	A very simple binary-safe text-based data container format. The data itself
	has no predefined meaning by this protocol and any meaning should be given
	by any super/subprotocols.

****************************************************************************/

#include "MaxsiEngine.h"

namespace MaxsiEngine
{

	MaxsiDataContainer::MaxsiDataContainer()
	{
		TemporaryVariable		=	NULL;
		VariableName			=	NULL;		
		Step					=	0;
		ExternalCache			=	NULL;
		InternalCache			=	NULL;
		TemporaryVariableCache	=	NULL;
		ChangedExternalCache	=	false;
	}

	MaxsiDataContainer::~MaxsiDataContainer()
	{
		ClearTemp();
		if ( VariableName )	{		delete[] VariableName; }
		if ( InternalCache ) {		delete[] InternalCache; }
	}

	bool	MaxsiDataContainer::Done()
	{
		ClearTemp();
		if ( VariableName ) {		delete[] VariableName; VariableName = NULL; }
		if ( InternalCache ) {		delete[] InternalCache; InternalCache = 0; }
		Step					=	0;
		ExternalCache			=	NULL;
		InternalCache			=	NULL;
		ChangedExternalCache	=	false;

		return true;
	}

	bool	MaxsiDataContainer::ClearTemp()
	{
		if ( TemporaryVariableCache ) { delete[] TemporaryVariableCache; TemporaryVariableCache = NULL; }
		if ( TemporaryVariable ) { DeleteList(TemporaryVariable); TemporaryVariable = NULL; }

		return true;
	}

	bool	MaxsiDataContainer::AppendToTemp(char* Input, size_t Size)
	{	
		if ( TemporaryVariableCache ) { delete[] TemporaryVariableCache; TemporaryVariableCache = NULL; }

		if ( !TemporaryVariable )
		{
			TemporaryVariable		=	CreateList(1);

			SetListEntry(TemporaryVariable,0,Input,Size);
		}
		else
		{
			SetListSize(TemporaryVariable,GetListSize(TemporaryVariable)+1);
			SetListEntry(TemporaryVariable,GetListSize(TemporaryVariable)-1,Input,Size);
		}

		return true;
	}

	char*	MaxsiDataContainer::GetTemp()
	{
		if ( TemporaryVariableCache ) { return TemporaryVariableCache; }

		TemporaryVariableCacheLen	=	0;

		size_t	NumEntries			=	GetListSize(TemporaryVariable);

		for ( size_t N = 0; N < NumEntries; N++ )
		{
			TemporaryVariableCacheLen	+=	GetListEntrySize(TemporaryVariable,N);
		}

		TemporaryVariableCache		=	new char[TemporaryVariableCacheLen+1];

		size_t	Written				=	0;
		size_t	Size				=	0;

		for ( size_t N = 0; N < NumEntries; N++ )
		{
			Size						=	GetListEntrySize(TemporaryVariable,N);
			
			memcpy(TemporaryVariableCache+Written,GetListEntry(TemporaryVariable,N),Size);

			Written						+=	Size;
		}

		TemporaryVariableCache[TemporaryVariableCacheLen]	=	0;

		return TemporaryVariableCache;		
	}

	bool	MaxsiDataContainer::GetNextVariable(char* Input, size_t Size, size_t &Read)
	{
		ExternalCache	=	NULL;

		if ( InternalCache ) { delete[] InternalCache; InternalCache = 0; }

		if ( ChangedExternalCache )
		{
			Input[Read]				=	OldExternalCache;
			ChangedExternalCache	=	false;
		}

		if ( Size )
		{
			if ( Step == 0 )
			{
				delete[] VariableName;
				VariableName				=	NULL;
				ClearTemp();
				Step++;
			}
			if ( Step == 1 )
			{
				char*	Variable			=	Input+Read;
				size_t	VariableLen			=	strlen_s(Variable,Size-Read);

				if ( VariableLen == ULONG_MAX )
				{
					// We haven't recieved it all yet. Just store what we got.
					AppendToTemp(Input+Read,Size-Read);

					Read	+=	Size-Read;

					return false;
				}

				if ( TemporaryVariable )
				{ 
					VariableName			=	BuildString(2,GetTemp(),Variable);
					ClearTemp();
				}
				else
				{
					VariableName			=	BuildString(1,Variable);
				}

				Read	+=	VariableLen+1;

				Step++;
			}

			if ( Step == 2 )
			{
				char*	VariableLength		=	Input+Read;
				size_t	VariableLengthLen	=	strlen_s(VariableLength,Size-Read);

				if ( VariableLengthLen == ULONG_MAX )
				{
					// We haven't recieved it all yet. Just store what we got.
					AppendToTemp(Input+Read,Size-Read);

					Read	+=	Size-Read;

					return false;
				}

				if ( TemporaryVariable )
				{ 
					char*	VariableLenSTR	=	BuildString(2,GetTemp(),VariableLength);
					VariableLen				=	(size_t)atoi(VariableLenSTR);
					delete[] VariableLenSTR;

					ClearTemp();
				}
				else
				{
					VariableLen				=	(size_t)atoi(VariableLength);
				}

				VariableLeft				=	VariableLen;

				Read	+=	VariableLengthLen+1;

				Step++;
			}

			if ( Step == 3 )
			{
				if ( VariableLeft <= Size )
				{
					if ( TemporaryVariable == NULL )
					{
						ExternalCache	=	Input+Read;

						// Make sure the variable is zero-terminated,
						// even if we have to make our own local copy of it.
						// Although, avoid memory allocations are much as possible.
						if ( VariableLen )
						{
							if ( ExternalCache[VariableLen-1] != 0 )
							{
								if ( VariableLen < Size-Read )
								{
									// Change the input and restore it later when
									// called again.
									OldExternalCache			=	ExternalCache[VariableLen];
									ChangedExternalCache		=	true;
									ExternalCache[VariableLen]	=	0;
								}
								else
								{
									// There is no room for a zero-termination. Make
									// our own local copy as a last resort.
									InternalCache				=	new char[VariableLen+1];
									InternalCache[VariableLen]	=	0;
									memcpy(InternalCache,ExternalCache,VariableLen);
								}
							}
							else
							{
								// Appears the variable is already zero-terminated. We are safe!
							}
						}
						else
						{
							// Appears we have a zero-length variable. But still ensure that
							// the variable, if requested, is zero-terminated.

							if ( 0 < Size-Read )
							{
								// Change the input and restore it later when
								// called again.
								OldExternalCache			=	ExternalCache[0];
								ChangedExternalCache		=	true;
								ExternalCache[0]			=	0;
							}
							else
							{
								// There is no room for a zero-termination. Make
								// our own local copy as a last resort.
								InternalCache				=	new char[1];
								InternalCache[0]			=	0;
							}
						}
					}
					else
					{
						AppendToTemp(Input+Read,Size-Read);
					}
					
					Step			=	0;
					Read			+=	VariableLeft;
					VariableLeft	=	0;
					return true;	
				}
				else
				{
					AppendToTemp(Input+Read,Size-Read);
					VariableLeft	-=	Size-Read;
					Read			+=	Size-Read;
					return false;
				}
			}
		}
		return false;
	}

	char*	MaxsiDataContainer::Variable()
	{
		return VariableName;
	}

	// Zero-termiantion safe.
	char*	MaxsiDataContainer::VariableTemp()
	{
		if ( InternalCache )
		{
			return InternalCache;
		}
		else if ( ExternalCache )
		{
			return ExternalCache;
		}
		else
		{
			return GetTemp();
		}
	}

	// Zero-termiantion safe.
	char*	MaxsiDataContainer::VariableKeep()
	{
		char*	Result		=	new char[VariableLen+1];

		memcpy(Result,VariableTemp(),VariableLen);

		Result[VariableLen]	=	0;

		return Result;
	}

}
